from .base import BrokerBase, RecordsType
import asyncio
from common.exchange import get_async_binance_portfolio_exchange
from common.variables import TimeFrameType
from copy import deepcopy
from datetime import datetime
from typing import Dict


class BrokerSimulate(BrokerBase):
    cache_data_records: RecordsType

    def __init__(
        self,
        init_balances: Dict[str, Dict[str, float]],
        slip_rate: float,
        leverages: Dict[str, float],
    ):
        exchange = get_async_binance_portfolio_exchange()
        self.init_balances = init_balances
        self.slip_rate = slip_rate
        asyncio.get_event_loop().run_until_complete(exchange.load_markets())
        self.markets = deepcopy(exchange.markets)
        self.leverages = deepcopy(leverages)

    def reset(self):
        self.balances = deepcopy(self.init_balances)
        self.positions = {}

    def prepare_sliding(
        self,
        start: str,
        end: str,
        need_symbols: list[str],
        need_fnames: list[str],
        timeframe: TimeFrameType,
    ):
        self.cache_data_records = self.get_symbol_and_factors(
            start, end, need_symbols, need_fnames, timeframe
        )

    def slide(self):
        one_pair = next(iter(self.cache_data_records.keys()))
        if self.cache_data_records[one_pair]:
            for k, v in self.cache_data_records.items():
                self.data_records[k].append(self.cache_data_records[k].popleft())
            return True
        return False

    def get_cur_time(self):
        return datetime.fromtimestamp(self.get_data("default", "time", -1) / 1000)

    def _balance_add(self, balance: str, amount: float):
        if amount == 0:
            return
        if balance not in self.balances:
            self.balances[balance] = {"total": 0, "debt": 0}
        target = self.balances[balance]
        if amount >= 0:
            # 优先还债
            debt_change = -min(target["debt"], amount)
            total_change = amount + debt_change
        else:
            total_change = max(amount, -target["total"])  # 负数
            debt_change = total_change - amount
        target["total"] += total_change
        target["debt"] += debt_change
        if not (target["total"] or target["debt"]):
            self.balances.pop(balance)

    def _position_add(self, symbol: str, size: float, price: float):
        if size == 0:
            return None

        if symbol not in self.positions:
            self.positions[symbol] = {
                "symbol": symbol,
                "contracts": 0,
                "side": "long" if size > 0 else "short",
                "entryPrice": 0,
            }

        target = self.positions[symbol]
        reward = None
        last_fee = target["contracts"] * target["entryPrice"]
        last_contracts = target["contracts"]
        if target["side"] == "long":
            if target["contracts"] + size >= 0:
                target["contracts"] += size
            else:
                target["contracts"] = -(target["contracts"] + size)
                target["side"] = "short"

            if size > 0:
                target["entryPrice"] = (last_fee + size * price) / target["contracts"]
            elif target["side"] == "long":
                reward = -size * (price - target["entryPrice"])
            else:
                reward = last_contracts * (price - target["entryPrice"])
                target["entryPrice"] = price

        else:
            if target["contracts"] - size >= 0:
                target["contracts"] -= size
            else:
                target["contracts"] = -(target["contracts"] - size)
                target["side"] = "long"

            if size < 0:
                target["entryPrice"] = (last_fee - size * price) / target["contracts"]
            elif target["side"] == "short":
                reward = size * (target["entryPrice"] - price)
            else:
                reward = last_contracts * (target["entryPrice"] - price)
                target["entryPrice"] = price

        if target["contracts"] == 0:
            self.positions.pop(symbol)
        if reward:
            quote = self.markets[symbol]["quote"]
            self._balance_add(quote, reward)

    def order_future(
        self,
        symbol: str,
        size: float,
        price: float,
        timeInForce: str,
        market_type,
    ) -> bool:
        if size == 0:
            return False
        if size > 0:
            match_price = self._get_match_price(
                symbol, price, market_type == "market", is_over=True
            )
        else:
            match_price = self._get_match_price(
                symbol, price, market_type == "market", is_over=False
            )
        if match_price is None:
            print(
                f"{self.get_cur_time()}, 期货做多（未能成交）：{symbol} 价格：{match_price}, 数量：{size}"
            )
            return False  # 未能成交

        old_size = self.get_position_size(symbol)
        if size * old_size <= 0:
            if abs(size) <= abs(old_size):
                self._position_add(symbol, size, match_price)
                return True
            else:
                self._position_add(symbol, -old_size, match_price)
                delta_size = size + old_size
                need_margin = (
                    match_price
                    * abs(delta_size)
                    * self.markets[symbol].get("contractSize", 1)
                    / self.get_leverage(symbol)
                )
                if need_margin > self.get_free_margin(self.markets[symbol]["quote"]):
                    print(
                        f"保证金不足 {symbol} 输入size {size} 剩余size {delta_size} price {price}"
                    )
                    return False
                else:
                    self._position_add(symbol, delta_size, match_price)
                    print(
                        f"{self.get_cur_time()}, 期货加仓：{symbol} 价格：{match_price}, 数量：{size}"
                    )
        else:
            need_margin = (
                match_price
                * abs(size)
                * self.markets[symbol].get("contractSize", 1)
                / self.get_leverage(symbol)
            )
            if need_margin > self.get_free_margin(self.markets[symbol]["quote"]):
                print(f"保证金不足 {symbol} 输入size {size} price {price}")
                return False
            self._position_add(symbol, size, match_price)
            print(
                f"{self.get_cur_time()}, 期货加仓：{symbol} 价格：{match_price}, 数量：{size}"
            )

    def order_spot(
        self,
        symbol: str,
        size: float,
        price: float,
        timeInForce: str,
        market_type: str,
    ):

        if size == 0:
            return

        settle_quote = self.markets[symbol]["quote"]
        settle_base = self.markets[symbol]["base"]
        if size > 0:
            match_price = self._get_match_price(
                symbol, price, market_type == "market", is_over=True
            )
        else:
            match_price = self._get_match_price(
                symbol, price, market_type == "market", is_over=False
            )
        if match_price is None:
            print(
                f"{self.get_cur_time()}, 现货买入（未能成交）：{symbol} 价格：{match_price}, 数量：{size}"
            )
            return False  # 未能成交

        old_size = self.get_balance_size(settle_base)
        if size * old_size <= 0:
            if abs(size) < abs(old_size):
                # 不用考虑保证金
                self._balance_add(settle_base, size)
                self._balance_add(settle_quote, -match_price * size)
            else:
                self._balance_add(settle_base, -old_size)
                self._balance_add(settle_quote, match_price * old_size)
                delta_size = size + old_size

                need_quote_debt = -delta_size * match_price
                need_margin = need_quote_debt / self.get_leverage(symbol)
                if need_margin > self.get_free_margin(settle_quote):
                    print(
                        f"现货保证金不足 {symbol}  原始size {size}  剩余size {delta_size} price {price}"
                    )

                self._balance_add(settle_base, delta_size)
                self._balance_add(settle_quote, -match_price * delta_size)
        else:
            if size > 0:
                pass

        old_quote_size = self.get_balance_size(settle_quote)
        use_quote = match_price * size
        if use_quote < old_quote_size:
            pass
        else:
            need_quote_debt = match_price * size

        self._balance_add(settle_quote, -match_price * size)
        self._balance_add(settle_base, size)
        print(
            f"{self.get_cur_time()}, 现货买入：{symbol} 价格：{match_price}, 数量：{size}"
        )
        print(f"资金：{self.balances}")
        return True
